package org.demo.rc.server;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.swing.JButton;
import javax.swing.JFrame;

import org.demo.rc.MonitorUtils;
import org.demo.rc.UDPImageMessage;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 * 远程控制：被控端
 * @author  
 * @date    2010-10-21
 * @file    org.demo.rc.MonitorServer.java
 */
public class MonitorServer {
	
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		final MonitorServer t = new MonitorServer();
		// 
		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
		int width = 300, height = 200;
		int left = (int)(screen.getWidth()-width)/2;
		int top = (int)(screen.getHeight()-height)/2;
		t.win.setBounds(left, top, width, height);
		t.win.setLayout(new BorderLayout());
		//
		JButton btnExit = new JButton("退出");
		btnExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				t.close();
			}
		});
		t.win.add(btnExit, BorderLayout.CENTER);
		// 		
		t.win.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				t.close();
			}
		});
		t.win.setVisible(
			  Boolean.parseBoolean(t.getProperties()
				                    .getProperty("win_visible", "false")));		
		// 
		t.cmd_task.start();
		t.data_task.start();
		// 创建自启动项
		MonitorUtils.autoRun("org.demo.rc.server.MonitorServer");
	}
	// 主窗体
	private JFrame win = null;
	private Robot robot = null;	
	private boolean closed = false;
	private Properties properties = null;
	// 
	private int cmd_port = 9980;
	private DatagramSocket cmd_socket = null;
	private DatagramSocket data_socket = null;
	private Queue<String> task_queue = null;
	private Map<String,String> port_map = new HashMap<String,String>();
	// 
	private Thread cmd_task = null;
	private Thread data_task = null;	
	// 数据缓冲块大小 10k
	private int data_buf_size = 10240;
	/**
	 * 构造函数
	 * @throws SocketException
	 * @throws AWTException 
	 */
	public MonitorServer() throws SocketException, AWTException{
		// load config
		URL url = this.getClass().getResource("server.properties");
		InputStream in = null;
		try {
			in = url.openStream();
			properties = new Properties();
			properties.load(in);
		} catch (Exception e){
			System.out.println("init:> " + e.getMessage());
		} finally {
			try { if (in != null)in.close(); } catch (Exception e){}
		}
		// initialize config
		cmd_port = Integer.parseInt(properties
				                   .getProperty("cmd_port", "9980"));
		// 
		JFrame.setDefaultLookAndFeelDecorated(true);
		this.robot = new Robot();
		this.win = new JFrame("被控端");
		this.cmd_socket = new DatagramSocket(cmd_port);
		this.data_socket = new DatagramSocket();
		this.task_queue = new ConcurrentLinkedQueue<String>();
		this.cmd_task = new Thread(new CmdTask(this));
		this.data_task = new Thread(new DataTask(this));
	}
	/**
	 * 获取配置信息
	 * @return
	 */
	private Properties getProperties(){
		return this.properties;
	}
	/**
	 * 获取命令通道
	 * @return
	 */
	private DatagramSocket getCmdSocket(){
		return this.cmd_socket;
	}
	/**
	 * 获取数据通道
	 * @return
	 */
	private DatagramSocket getDataSocket(){
		return this.data_socket;
	}
	/**
	 * 获取任务队列
	 * @return
	 */
	private Queue<String> getTaskQueue(){
		return this.task_queue;
	}
	/**
	 * 获取端口映射
	 * @return
	 */
	private Map<String,String> getPortMap(){
		return this.port_map;
	}
	/**
	 * 刷新图像
	 */
	private void refresh(InetAddress address){
		String ip = address.getHostAddress();
		task_queue.offer(ip);
	}
	/**
	 * 获取 robot
	 * @return
	 */
	private Robot getRobot(){
		return this.robot;
	}
	/**
	 * 获取数据缓冲块大小
	 * @return
	 */
	private int getDataBufSize(){
		return this.data_buf_size;
	}
	/**
	 * 设置数据缓冲块大小
	 * @param size
	 */
	private void setDataBufSize(int size){
		this.data_buf_size = size;
	}
	/**
	 * 判断程序是否已关闭
	 * @return
	 */
	private boolean isClosed(){
		return this.closed;
	}
	/**
	 * 关闭
	 */
	private void close(){
		win.dispose();
		closed = true;
		cmd_socket.close();
		data_socket.close();
	}
	/**
	 * 命令任务类
	 * @author 
	 *
	 */
	private class CmdTask implements Runnable{
		private MonitorServer parent = null;
		// 1k
		private int cmd_buf_size = 1024;
		/**
		 * 构造函数
		 * @param parent
		 */
		public CmdTask(MonitorServer parent){
			this.parent = parent;
			cmd_buf_size = Integer.parseInt(
					               parent.getProperties()
					              .getProperty("cmd_buf_size", "1")) * 1024;
		}
		/**
		 * 执行任务
		 */
		public void run() {
			byte[] buf = new byte[cmd_buf_size];
			String[] data = null;
			String cmd = null;
			int x,y,buttons,wheelAmt,keycode;
			while(!parent.isClosed()){
				try {
					DatagramPacket packet = new DatagramPacket(buf, buf.length);
					parent.getCmdSocket().receive(packet);
					data = new String(packet.getData(),packet.getOffset(),packet.getLength()).split(";");
					if (data.length < 1){
						continue;
					}
					cmd = data[0];
					if ("refresh".equalsIgnoreCase(cmd)){
						parent.refresh(packet.getAddress());
					} else if ("mouseMove".equalsIgnoreCase(cmd)){
						x = Integer.parseInt(data[1]);
						y = Integer.parseInt(data[2]);
						parent.getRobot().mouseMove(x, y);
					} else if ("mousePress".equalsIgnoreCase(cmd)){
						buttons = Integer.parseInt(data[1]);
						parent.getRobot().mousePress(buttons);
					} else if ("mouseRelease".equalsIgnoreCase(cmd)){
						buttons = Integer.parseInt(data[1]);
						parent.getRobot().mouseRelease(buttons);
					} else if ("mouseWheel".equalsIgnoreCase(cmd)){
						wheelAmt = Integer.parseInt(data[1]);
						parent.getRobot().mouseWheel(wheelAmt);
					} else if ("keyPressed".equalsIgnoreCase(cmd)){
						keycode = Integer.parseInt(data[1]);
						parent.getRobot().keyPress(keycode);
					} else if ("keyReleased".equalsIgnoreCase(cmd)){
						keycode = Integer.parseInt(data[1]);
						parent.getRobot().keyRelease(keycode);
					} else if ("connect".equalsIgnoreCase(cmd)){
						String ip = packet.getAddress().getHostAddress();
						String port = data[1];
						parent.getPortMap().put(ip, port);
					} else if ("disconnect".equalsIgnoreCase(cmd)){
						String ip = packet.getAddress().getHostAddress();
						parent.getPortMap().remove(ip);
					} else if ("data_buf_size".equalsIgnoreCase(cmd)){
						int size = Integer.parseInt(data[1]);
						parent.setDataBufSize(size);
					} else if ("exit".equalsIgnoreCase(cmd)){
						parent.close();
					}
				} catch (Exception e){
					System.out.println("cmd_task:> " + e.getMessage());
				}
			}
		}
	}// class CmdTask
	/**
	 * 数据任务类
	 * @author 
	 *
	 */
	private class DataTask implements Runnable{
		private MonitorServer parent = null;
		private Robot robot = null;
		private Rectangle screenRect = null;
		private DatagramPacket packet = null;
		private float img_quality = 0.1f;
		private int wait_timeout = 2000;
		private int send_data_interval = 50;
		/**
		 * 构造函数
		 * @param parent
		 * @throws AWTException 
		 */
		public DataTask(MonitorServer parent) throws AWTException{
			this.parent = parent;
			// 
			Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();   
			this.screenRect = new Rectangle(dimension);
			this.robot = new Robot();
			// 
			img_quality = Float.parseFloat(
					            parent.getProperties()
					           .getProperty("img_quality", "0.5f"));
			wait_timeout = Integer.parseInt(
					               parent.getProperties()
					              .getProperty("wait_timeout", "2000"));
			send_data_interval = Integer.parseInt(
					                     parent.getProperties()
					                    .getProperty("send_data_interval", "50"));
		}
		/**
		 * 执行任务
		 */
		public void run() {
			byte[] data = null;			
			UDPImageMessage[] messages = null;
			while (!parent.isClosed()){
				try {
					String ip = parent.getTaskQueue().poll();
					String port = parent.getPortMap().get(ip);
					if (ip == null || port == null){
						// 没有任务时等两秒
						if (wait_timeout > 0){
						    Thread.sleep(wait_timeout);
						}
						continue;
					}
					data = getNewScreen();
					// 分包
					messages = pack(data);
					sendMessage(ip,port,messages);
				} catch (Exception e) {
					System.out.println("data_task:> " + e.getMessage());
				}
			}
		}
		/**
		 * 获取当前屏幕图像
		 * @return
		 * @throws IOException 
		 */
		public byte[] getNewScreen() throws IOException{
			BufferedImage image = robot.createScreenCapture(screenRect);
			JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
			param.setQuality(img_quality, false);
			// 
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out,param);
			encoder.encode(image);
			out.flush();
			// 
			return out.toByteArray();
		}
		/**
		 * 将字节数组分成适合 UDP 发送的数据包
		 * @param data
		 * @return
		 */
		public UDPImageMessage[] pack(byte[] data){
			String md5 = MonitorUtils.encryptMd5(data);
			// 当前这批数据的时间戳
			long time = System.currentTimeMillis();
			// 为确保摘要的唯一性，加上时间戳再计算一次摘要
			md5 = MonitorUtils.encryptMd5((md5 + time).getBytes());
			int total = data.length;
			int buffer = parent.getDataBufSize() - 80;
			int size = (total/buffer) + 1;
			if (total%buffer == 0){
				size--;
			}
			int index = 0;
			byte[] pack = null;
			UDPImageMessage[] messages = new UDPImageMessage[size];
			while (total > 0){
				if (total > buffer){
					pack = new byte[buffer];
					for (int i=0; i<pack.length; i++){
						pack[i] = data[index*buffer + i];
					}
					messages[index] = new UDPImageMessage(md5,size,index,pack);
					messages[index].setTime(time);
					total = total - buffer;
					index++;
				} else {
					pack = new byte[total];
					for (int i=0; i<pack.length; i++){
						pack[i] = data[index*buffer + i];
					}
					messages[index] = new UDPImageMessage(md5,size,index,pack);
					messages[index].setTime(time);
					total = 0;
					index++;
				}
			}			
			return messages;
		}
		/**
		 * 发送数据
		 * @param ip
		 * @param port
		 * @param messages
		 * @throws IOException 
		 * @throws InterruptedException 
		 */
		public void sendMessage(String ip,String port,UDPImageMessage[] messages) throws IOException, InterruptedException{
			for (int i=0; i<messages.length; i++){				
				packet = createDataPack(ip, Integer.parseInt(port), messages[i].pack());
				parent.getDataSocket().send(packet);
				// 此处最好暂停一下，否则短时间内发送大量数据，客户端来不及接收，就会丢失数据
				if (send_data_interval > 0){
				    Thread.sleep(send_data_interval);
				}
			}
		}
		/**
		 * 创建发送数据包
		 * @param host
		 * @param port
		 * @param data
		 * @return
		 */
		public DatagramPacket createDataPack(String host,int port,byte[] buf){
			try {
				InetAddress address = InetAddress.getByName(host);
				return new DatagramPacket(buf,buf.length,address,port);
			} catch(Exception e){
				throw new RuntimeException(e);
			}
		} 
	}// class DataTask
}
